package jmind.core.aspect;

/**
 * Created by weibo.xwb on 2017/9/19.
 */

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import jmind.base.annotation.CacheMonitor;
import jmind.base.cache.ICache;
import jmind.base.util.DataUtil;
import jmind.base.util.GlobalConstants;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.util.*;
import java.util.concurrent.ConcurrentMap;

/**
 * @author wbxie
 * @CacheMonitor( key = "'user",exp=3600)
 * @CacheMonitor(name = "cacheName", prefix = "userid",key="#user.cid,#name",exp=3600)
 * <p>
 * key 用做前缀+ 方法参数值
 * @Component
 * @Aspect 好像什么时候都需要打这个标签
 * 2013-12-4
 * <aop:aspectj-autoproxy proxy-target-class="true" />
 * <aop:config expose-proxy="true"  />
 * @EnableAspectJAutoProxy(proxyTargetClass = true,exposeProxy = true)
 */
public abstract class AbstractCacheAspect {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    public abstract ICache getCache(CacheMonitor monitor);


    public Object getByCache(MethodSignature signature,CacheMonitor monitor,ICache cache,String key) throws Throwable {
        return  cache.getObject(key,null);
    }

    public  Map<String, Object> getByCache(MethodSignature signature,CacheMonitor monitor,ICache cache,Collection<String> keys){
        return cache.getObjects(keys,null);
    }




    final ExpressionParser parser = new SpelExpressionParser();
    final ConcurrentMap<String, List<Expression>> expressionMap = Maps.newConcurrentMap();


    private final List<Expression> getExpressions(String spel) {
        List<Expression> list = expressionMap.get(spel);
        if (list == null) {
            list = Lists.newArrayList();
            StringTokenizer token = new StringTokenizer(spel, GlobalConstants.COMMA);
            while (token.hasMoreTokens()) {
                list.add(parser.parseExpression(token.nextToken()));
            }
            expressionMap.put(spel, list);
        }
        return list;
    }


    @Around(value = "execution(* *(..)) && @annotation(monitor)", argNames = "pjp,monitor")
    public Object doAround(ProceedingJoinPoint pjp, CacheMonitor monitor) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        ICache cache = getCache(monitor);
        if (cache == null) {
            logger.warn(monitor.type() + " cache is null " + signature.toShortString());
            return pjp.proceed();
        }

        String key;
        if (monitor.key().isEmpty()) {
            Object[] values = pjp.getArgs();
            // 如果入参只有一个且为Collection, 返回值为Map, 则调用collectionCache方法,支持 多key 查询
            if (values.length == 1 && values[0] instanceof Collection && Map.class.isAssignableFrom(signature.getReturnType())) {
                return collectionCache(pjp, monitor, signature, cache, values);
            }
            key = DataUtil.composeKey(monitor.prefix(), pjp.getArgs());
        } else {
            StandardEvaluationContext context = new StandardEvaluationContext();
            String[] names = signature.getParameterNames(); // 参数变量名
            Object[] values = pjp.getArgs(); // 参数值
            for (int i = 0; i < names.length; i++) {
                context.setVariable(names[i], values[i]);
            }

            StringBuilder sb = new StringBuilder(monitor.prefix());
            List<Expression> expressions = getExpressions(monitor.key());
            for (Expression exp : expressions) {
                sb.append(GlobalConstants.DASH).append(exp.getValue(context));
            }
            key = sb.toString();
        }


        //添加参数值 做为key
        if (monitor.remove()) {
            Object object = pjp.proceed();
            cache.delete(key);
            return object;
        }
        Object obj = getByCache(signature,monitor,cache,key);
        if (obj == null) { // 缓存不存在
            obj = pjp.proceed();
            if (obj != null) {
                cache.set(key, monitor.exp(), obj);
            }
            return obj;
        }
        return obj;

    }

    /**
     * 代码描述 : 入参为Collection时的缓存方法
     *
     * @param values 切入的方法的入参
     *               date : 2018/7/25
     *               modified by : [变更日期YYYY-MM-DD][更改人姓名][变更描述]
     * @author subo177693
     */
    private Map collectionCache(ProceedingJoinPoint pjp, CacheMonitor monitor, MethodSignature signature, ICache cache, Object[] values)
            throws Throwable {
        //获取入参collection
        Collection inputCollection = (Collection) values[0];
        // key的集合
        Collection<String> keyCollection = new ArrayList<>(inputCollection.size());
        // key与入参的对应关系
        Map<Object, String> inputAndkeyRelation = new HashMap<>(inputCollection.size());
        for (Object o : inputCollection) {
            if (o != null && !inputAndkeyRelation.containsKey(o)) {
                String singleKey = DataUtil.composeKey(monitor.prefix(), o);
                inputAndkeyRelation.put(o, singleKey);
                keyCollection.add(singleKey);
            }
        }
        // 从缓存获取有缓存的map
        Map<String, ?> multiResult = getByCache(signature,monitor,cache,keyCollection);
        // 有缓存的结果map
        Map cacheResult = new HashMap(inputCollection.size());
        Collection inputCollectionNew = newColection(inputCollection);
        for (Object o : inputCollection) {
            Object cacheValue = multiResult.get(inputAndkeyRelation.get(o));
            if (cacheValue != null) {
                cacheResult.put(o, cacheValue);
            } else {
                inputCollectionNew.add(o);
            }
        }

        // 入参去除有缓存的input参数
        if (inputCollectionNew.isEmpty()) {
            return cacheResult;
        }
        // 重新赋值入参
        values[0] = inputCollectionNew;
        // 对剩余的input参数执行方法
        Map<Object, Object> noCacheResult = (Map) pjp.proceed(values);
        for (Map.Entry entry : noCacheResult.entrySet()) {
            cache.set(inputAndkeyRelation.get(entry.getKey()), monitor.exp(), entry.getValue());
        }
        // 合并结果
        cacheResult.putAll(noCacheResult);
        return cacheResult;
    }


    private Collection newColection(Collection inputCollection) {
        Class<? extends Collection> aClass = inputCollection.getClass();
        if (aClass.isInterface()) {
            return inputCollection instanceof Set ? new HashSet() : new ArrayList();
        }
        try {
            return aClass.newInstance();
        } catch (Exception e) {
            return inputCollection instanceof Set ? new HashSet() : new ArrayList();
        }
    }




}